package controller

import (
	"60321/pkg/global"
	"60321/pkg/models"
	"60321/pkg/models/requests"
	"encoding/json"
	"github.com/gin-gonic/gin"
	"github.com/go-redis/redis/v8"
	"github.com/streadway/amqp"
	"gorm.io/gorm"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"
)

func BuyTicket(ctx *gin.Context) {
	var request requests.BuyRequest
	if err := ctx.ShouldBind(&request); err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// 使用Hash存储库存
	hashKey := "ticket_stocks"
	field := strconv.FormatInt(int64(request.TicketID), 10)

	// 获取分布式锁 - 提前获取，覆盖整个业务逻辑
	lockKey := "ticket_lock:" + strconv.FormatInt(int64(request.TicketID), 10)
	token, err := global.TryLock(ctx, lockKey, *global.Redis)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	defer global.Unlock(ctx, lockKey, token, *global.Redis)

	// 1. 检查库存
	stockVal, err := global.Redis.HGet(ctx, hashKey, field).Int64()
	if err != nil {
		if err == redis.Nil {
			// 从数据库加载库存
			var dbStock int64
			if err := global.Db.Model(&models.TicketModel{}).
				Where("id = ?", request.TicketID).
				Select("issuance_quantity").
				Scan(&dbStock).Error; err != nil {
				ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
				return
			}

			// 初始化Hash字段
			if err := global.Redis.HSet(ctx, hashKey, field, dbStock).Err(); err != nil {
				ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
				return
			}
			stockVal = dbStock
		} else {
			ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
	}

	if stockVal <= 0 {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": "票已售空"})
		return
	}

	// 2. 再次检查库存(双检)
	stockVal, err = global.Redis.HGet(ctx, hashKey, field).Int64()
	if err != nil || stockVal <= 0 {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": "票已售空"})
		return
	}

	// 3. 扣减库存(使用Hash原子操作)
	_, err = global.Redis.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
		pipe.HIncrBy(ctx, hashKey, field, -1)
		return nil
	})
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	var order models.UserTicketModel
	// 4. 使用数据库事务执行后续操作	确保这里的耗时操作可以做完
	err = global.Db.Transaction(func(tx *gorm.DB) error {
		// 查询城市ID
		var cityId uint
		if err := tx.Model(&models.CityModel{}).Where("city_name = ?", request.Destination).
			Select("id").Scan(&cityId).Error; err != nil {
			return err
		}
		// 查询价格
		var price int
		if err := tx.Model(&models.TicketCityModel{}).
			Where("ticket_id = ? and city_id = ?", request.TicketID, cityId).
			Select("price").Scan(&price).Error; err != nil {
			return err
		}
		// 创建订单
		order = models.UserTicketModel{
			UserID:      request.UserID,
			TicketID:    request.TicketID,
			PayTime:     time.Now(),
			PayWay:      request.PayWay,
			Destination: request.Destination,
			Price:       price,
		}
		return nil
	})

	if err != nil {
		// 事务失败，恢复库存（在锁的保护下）
		global.Redis.HIncrBy(ctx, hashKey, field, 1)
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	// 5. 事务成功后发送消息到队列
	if err := sendOrderToQueue(order, global.Mq); err != nil {
		// 订单数据在这里进行保存，出现问题在这里回滚
		global.Redis.HIncrBy(ctx, hashKey, field, 1)
		log.Printf("Failed to send order to queue: %v", err)
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	var cityName string
	if err := global.Db.Model(models.TicketModel{}).Select("destination").
		Where("id = ?", request.TicketID).
		Scan(&cityName).Error; err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	var city models.CityModel
	if err := global.Db.Take(&city, "city_name = ?", cityName).Error; err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	feature := city.UrbanCharacteristics // "hotel:HelloWorld,scenicSpot:helloWorld"
	var CityTaxis []models.CityTaxiModel
	global.Db.Model(&models.CityTaxiModel{}).Preload("TaxiModel").
		Where("city_id = ?", city.ID).Find(&CityTaxis)

	var taxis []models.TaxiModel
	for _, v := range CityTaxis {
		taxis = append(taxis, v.TaxiModel)
	}
	var hotel = strings.Split(feature, ",")[0]
	var scenicSpot = strings.Split(feature, ",")[1]
	hotel = strings.Split(hotel, ":")[1]
	scenicSpot = strings.Split(scenicSpot, ":")[1]

	// 根据你买的票的目的地进行推荐 （酒店，出租车，景点）
	ctx.JSON(http.StatusOK, gin.H{"city_hotel": hotel, "scenicSpot": scenicSpot, "city_taxi": taxis})
	// 这里操作虽然耗时，但是不会修改数据，不影响数据一致性
}

func sendOrderToQueue(order models.UserTicketModel, mq *amqp.Connection) error {
	ch, err := mq.Channel()
	if err != nil {
		return err
	}
	defer ch.Close()

	q, err := ch.QueueDeclare(
		"order_queue", // 队列名
		true,          // 持久化
		false,         // 自动删除
		false,         // 排他性
		false,         // 不等待
		nil,           // 参数
	)
	if err != nil {
		return err
	}
	body, err := json.Marshal(order)
	if err != nil {
		return err
	}
	err = ch.Publish(
		"",     // 交换器
		q.Name, // 路由键
		false,  // 强制
		false,  // 立即
		amqp.Publishing{
			ContentType: "application/json",
			Body:        body,
		})
	return err
}

type Data struct {
	TicketId uint
	City     []models.CityModel
}

func GetTickets(c *gin.Context) {
	var tickets []models.TicketModel
	if err := global.Db.Find(&tickets, "issuance_quantity != 0").Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	// 把途经城市也返回过去
	var data = make([]Data, len(tickets))
	for i := 1; i <= len(tickets); i++ {
		data[i-1].TicketId = tickets[i-1].ID
		var citiesTicket []models.TicketCityModel
		global.Db.Model(&models.TicketCityModel{}).Preload("CityModel").
			Where("ticket_id = ?", tickets[i-1].ID).Order("city_order asc").Find(&citiesTicket)
		for _, city := range citiesTicket {
			data[i-1].City = append(data[i-1].City, city.CityModel)
		}
	}
	c.JSON(http.StatusOK, gin.H{"tickets": tickets, "cities": data})
}
func GetTicket(c *gin.Context) {
	id := c.Param("id")
	var ticket models.TicketModel
	if err := global.Db.Take(&ticket, id).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	var citiesTicket []models.TicketCityModel
	global.Db.Model(&models.TicketCityModel{}).Preload("CityModel").
		Where("ticket_id = ?", ticket.ID).
		Order("city_order asc").
		Find(&citiesTicket)
	var data []models.CityModel
	for _, city := range citiesTicket {
		data = append(data, city.CityModel)
	}
	c.JSON(http.StatusOK, gin.H{"ticket": ticket, "cities": data})
}

func GetTicketByCity(context *gin.Context) {
	var request requests.GetTicketByCityRequest
	if err := context.ShouldBind(&request); err != nil {
		context.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	var ticket models.TicketModel
	global.Db.Model(&ticket).Where("origin = ? and destination = ?", request.Origin, request.Destination).
		Scan(&ticket)
	context.JSON(http.StatusOK, gin.H{"ticket": ticket})
}
